Theories and practice of polymorphism |
---|
In object-oriented programming, a virtual function or virtual method is a function or method whose behaviour can be overridden within an inheriting class by a function with the same signature. This concept is a very important part of the polymorphism portion of object-oriented programming (OOP).
Contents |
The concept of the virtual function solves the following problem:
In OOP when a derived class inherits a base class, an object of the derived class may be referred to (or cast) as either being the base class type or the derived class type. If there are base class methods overridden by the derived class, the method call behaviour is ambiguous.
The distinction between virtual and non-virtual resolves this ambiguity. If the function in question is designated virtual
in the base class then the derived class' function would be called (if it exists). If it is not virtual, the base class' function would be called.
Virtual functions overcome the problems with the type-field solution by allowing the programmer to declare functions in a base class that can be redefined in each derived class.
In C++ virtual methods are declared by prepending the virtual keyword to the function's declaration.
For example, a base class Animal
could have a virtual function eat
. Subclass Fish
would implement eat()
differently than subclass Wolf
, but you can invoke eat()
on any class instance referred to as Animal, and get the eat()
behaviour of the specific subclass.
This allows a programmer to process a list of objects of class Animal
, telling each in turn to eat (by calling eat()
), with no knowledge of what kind of animal may be in the list. You also do not need to have knowledge of how each animal eats, or what the complete set of possible animal types might be.
In C#, a base class must provide the virtual modifier for any virtual method, and derived classes must provide the override modifier for any overridden method inherited from a base class. The following is an example in C#.NET
using System; using System.Collections.Generic; namespace ConsoleApplication1 { public class Animal { public virtual void Eat() { Console.WriteLine("I eat like a generic Animal."); } } public class Wolf : Animal { public override void Eat() { Console.WriteLine("I eat like a wolf!"); } } public class Fish : Animal { public override void Eat() { Console.WriteLine("I eat like a fish!"); } } public class GoldFish : Fish { public override void Eat() { Console.WriteLine("I eat like a goldfish!"); } } public class OtherAnimal : Animal { // Eat() method is not overridden, so the base class method will be used. } public class Program { public static void Main(string[] args) { IList<Animal> animals = new List<Animal>(); animals.Add(new Animal()); animals.Add(new Wolf()); animals.Add(new Fish()); animals.Add(new GoldFish()); animals.Add(new OtherAnimal()); foreach (Animal currentAnimal in animals) { currentAnimal.Eat(); } } } }
Output:
I eat like a generic Animal. I eat like a wolf! I eat like a fish! I eat like a goldfish! I eat like a generic Animal.
The following is an example in C++.
#include <iostream> #include <memory> #include <vector> class Animal { public: virtual void eat() { std::cout << "I eat like a generic animal.\n"; } // polymorphic deletes require a virtual base destructor virtual ~Animal() { } }; class Wolf : public Animal { public: void eat() { std::cout << "I eat like a wolf!\n"; } }; class Fish : public Animal { public: void eat() { std::cout << "I eat like a fish!\n"; } }; class GoldFish : public Fish { public: void eat() { std::cout << "I eat like a goldfish!\n"; } }; class OtherAnimal : public Animal { }; int main() { std::vector<std::unique_ptr<Animal>> animals; animals.push_back(std::unique_ptr<Animal>(new Animal)); animals.push_back(std::unique_ptr<Animal>(new Wolf)); animals.push_back(std::unique_ptr<Animal>(new Fish)); animals.push_back(std::unique_ptr<Animal>(new GoldFish)); animals.push_back(std::unique_ptr<Animal>(new OtherAnimal)); for (auto it = animals.begin(); it != animals.end(); ++it) { (*it)->eat(); } }
Output with the virtual function Animal::eat():
I eat like a generic animal. I eat like a wolf! I eat like a fish! I eat like a goldfish! I eat like a generic animal.
Output if Animal::eat() were not declared as virtual:
I eat like a generic animal. I eat like a generic animal. I eat like a generic animal. I eat like a generic animal. I eat like a generic animal.
The D programming language implements virtual functions. The override keyword tells the compiler that we don't want to define a new function, but rather override an existing one. Thus, if no function with the same name exists in the base class, the compiler breaks with an error. Note that the example below would produce the same output without the override keyword, but would also throw a compilation warning: Error: overrides base class function test.main.Animal.eat, but is not marked with 'override'.
import std.stdio; void main() { class Animal { void eat() { writefln("I eat like a generic Animal."); } } class Wolf : Animal { override void eat() { writefln("I eat like a Wolf."); } } class Fish : Animal { override void eat() { writefln("I eat like a Fish."); } } class GoldFish : Fish { override void eat() { writefln("I eat like a GoldFish."); } } class OtherAnimal : Animal { } Animal[] animals = new Animal[5]; animals[0] = new Animal; animals[1] = new Wolf; animals[2] = new Fish; animals[3] = new GoldFish; animals[4] = new OtherAnimal; foreach ( Animal a; animals) a.eat(); }
Output:
I eat like a generic Animal. I eat like a wolf! I eat like a fish! I eat like a goldfish! I eat like a generic Animal.
In Delphi, virtual methods are marked with virtual keyword. Methods in descendant classes that override a virtual method in ancestor class are marked with override keyword. If a virtual method is defined in an ancestor class, but not defined in a descendant class, descendant class will use ancestor class' implementation of that method.
Delphi also provides another similar method type called; "Dynamic method". Dynamic methods are marked with dynamic keyword instead of virtual keyword. Dynamic methods in Delphi have the same functionality of Virtual methods, but Delphi compiler implements them differently. In general, Virtual methods have better performance than Dynamic methods.[1] Here is an example of Virtual methods in Delphi:
{ Module 1: AnimalTypes.pas } unit AnimalTypes; interface type Animal = class public procedure Eat; virtual; end; Wolf = class(Animal) public procedure Eat; override; end; Fish = class(Animal) public procedure Eat; override; end; GoldFish = class(Fish) public procedure Eat; override; end; OtherAnimal = class(Animal) public { Eat method is not overridden, so the base class method will be used. } end; implementation { Animal } procedure Animal.Eat; begin WriteLn('I eat like a generic Animal.'); end; { Wolf } procedure Wolf.Eat; begin WriteLn('I eat like a wolf!'); end; { Fish } procedure Fish.Eat; begin WriteLn('I eat like a fish!'); end; { GoldFish } procedure GoldFish.Eat; begin WriteLn('I eat like a goldfish!'); end; end.
{ Module 2: ConsoleApplication1.dpr } program ConsoleApplication1; {$APPTYPE CONSOLE} uses Generics.Collections, AnimalTypes; var Animals : TList<Animal>; CurrentAnimal : Animal; begin Animals := TList<Animal>.Create; try Animals.Add(Animal.Create); Animals.Add(Wolf.Create); Animals.Add(Fish.Create); Animals.Add(GoldFish.Create); Animals.Add(OtherAnimal.Create); for CurrentAnimal in Animals do CurrentAnimal.Eat; finally Animals.Free; end; end.
Output:
I eat like a generic Animal. I eat like a wolf! I eat like a fish! I eat like a goldfish! I eat like a generic Animal.
Groovy has exactly the same OOP characteristics as Java and the Java code shown below will behave exactly the same way. However, one would probably write the example in a more concise way in Groovy, in order to make the code easier to read:
class Animal { void eat() { println "I eat like a generic Animal." } } class Wolf extends Animal { void eat() { println "I eat like a wolf!" } } class Fish extends Animal { void eat() { println "I eat like a fish!" } } class Goldfish extends Fish { void eat() { println "I eat like a goldfish!" } } class OtherAnimal extends Animal {} for (animal in [ new Animal(), new Wolf(), new Fish(), new Goldfish(), new OtherAnimal() ]) { animal.eat() }
In Java, all non-static methods are by default "virtual functions". Only methods marked with the keyword final, which cannot be overridden, along with private methods, which are not inherited, are non-virtual. The following is an example of virtual methods in Java:
import java.util.*; public class Animal { public void eat() { System.out.println("I eat like a generic Animal."); } public static void main(String[] args) { List<Animal> animals = new LinkedList<Animal>(); animals.add(new Animal()); animals.add(new Wolf()); animals.add(new Fish()); animals.add(new Goldfish()); animals.add(new OtherAnimal()); for (Animal currentAnimal : animals) { currentAnimal.eat(); } } } public class Wolf extends Animal { @Override public void eat() { System.out.println("I eat like a wolf!"); } } public class Fish extends Animal { @Override public void eat() { System.out.println("I eat like a fish!"); } } public class Goldfish extends Fish { @Override public void eat() { System.out.println("I eat like a goldfish!"); } } public class OtherAnimal extends Animal {}
Output:
I eat like a generic Animal. I eat like a wolf! I eat like a fish! I eat like a goldfish! I eat like a generic Animal.
In Python all class functions (methods) are virtual.
class Animal: def eat(self): print "I eat like a generic Animal." class Wolf(Animal): def eat(self): print "I eat like a wolf!" class Fish(Animal): def eat(self): print "I eat like a fish!" class GoldFish(Fish): def eat(self): print "I eat like a goldfish!" class OtherAnimal(Animal): pass animals = [ Animal(), Wolf(), Fish(), GoldFish(), OtherAnimal() ] for a in animals: a.eat()
Output:
I eat like a generic Animal. I eat like a wolf! I eat like a fish! I eat like a goldfish! I eat like a generic Animal.
In Ruby all instance methods are virtual.
class Animal def eat puts "I eat like a generic Animal." end end class Wolf < Animal def eat puts "I eat like a wolf!" end end class Fish < Animal def eat puts "I eat like a fish!" end end class GoldFish < Fish def eat puts "I eat like a goldfish!" end end class OtherAnimal < Animal end animals = [ Animal.new, Wolf.new, Fish.new, GoldFish.new, OtherAnimal.new ] animals.each {|a| a.eat }
Output:
I eat like a generic Animal. I eat like a wolf! I eat like a fish! I eat like a goldfish! I eat like a generic Animal.
In VB.NET, a base class must provide the Overridable modifier for any virtual method, and derived classes may provide the optional Overrides modifier for any overridden method inherited from a base class (this prevents a warning from being issued). The following is an example in VB.NET:
Imports System Imports System.Collections.Generic Namespace ConsoleApplication1 Public Class Animal Public Overridable Sub Eat() Console.WriteLine("I eat like a generic Animal.") End Sub End Class Public Class Wolf Inherits Animal Public Overrides Sub Eat() Console.WriteLine("I eat like a wolf!") End Sub End Class Public Class Fish Inherits Animal Public Overrides Sub Eat() Console.WriteLine("I eat like a fish!") End Sub End Class Public Class Goldfish Inherits Fish Public Overrides Sub Eat() Console.WriteLine("I eat like a goldfish!") End Sub End Class Public Class OtherAnimal Inherits Animal 'Eat() method is not overridden, so the base class method will be used. End Class Public Class Program Shared Sub Main() Dim animals As New List(Of Animal) animals.Add(New Animal()) animals.Add(New Wolf()) animals.Add(New Fish()) animals.Add(New Goldfish()) animals.Add(New OtherAnimal()) For Each currentAnimal As Animal In animals currentAnimal.Eat() Next End Sub End Class End Namespace
Output:
I eat like a generic Animal. I eat like a wolf! I eat like a fish! I eat like a goldfish! I eat like a generic Animal.
A pure virtual function or pure virtual method is a virtual function that is required to be implemented by a derived class, if that class is not abstract. Classes containing pure virtual methods are termed "abstract"; they cannot be instantiated directly. A subclass of an abstract class can only be instantiated directly if all inherited pure virtual methods have been implemented by that class or a parent class. Pure virtual methods typically have a declaration (signature) and no definition (implementation).
As an example, an abstract base class MathSymbol
may provide a pure virtual function doOperation()
, and derived classes Plus
and Minus
implement doOperation()
to provide concrete implementations. Implementing doOperation()
would not make sense in the MathSymbol
class, as MathSymbol
is an abstract concept whose behaviour is defined solely for each given kind (subclass) of MathSymbol
. Similarly, a given subclass of MathSymbol
would not be complete without an implementation of doOperation()
.
Although pure virtual methods typically have no implementation in the class that declares them, pure virtual methods in C++ are permitted to contain an implementation in their declaring class, providing fallback or default behaviour that a derived class can delegate to, if appropriate.
Pure virtual functions are also used where the method declarations are being used to define an interface for which derived classes will supply all implementations. An abstract class serving as an interface contains only pure virtual functions, and no data members or ordinary methods. Use of purely abstract classes as interfaces works in C++ as it supports multiple inheritance. Because many OO languages do not support multiple inheritance they often provide a separate interface mechanism. This is seen in Java for example.
In C++, pure virtual functions are declared using a special syntax (= 0), as demonstrated below.
class Abstract { public: virtual void pure_virtual() = 0; };
The pure virtual function declaration provides only the prototype of the method. Although an implementation of the pure virtual function is typically not provided in an abstract class, it may be included, although the definition may not be included at the point of declaration.[2] Every non-abstract child class is still required to override the method, but the implementation provided by the abstract class may be called in this way:
void Abstract::pure_virtual() { // Do something } class Child : public Abstract { virtual void pure_virtual(); // no longer abstract, this class may be // instantiated. }; void Child::pure_virtual() { Abstract::pure_virtual(); // the implementation in the abstract class // is executed }
In C# and Java, pure virtual methods are declared using the abstract
keyword. Such a method cannot have a body. A class containing abstract methods (either directly, or inherited and not overridden) must itself be declared abstract. (But the converse is not true - an abstract class is not required to have any abstract methods.) An abstract class cannot be instantiated. Any concrete (instantiable) class with an abstract class as its ancestor must implement all unimplemented abstract methods in order to successfully compile.
abstract class B { abstract void a_pure_virtual_function(); }
C# and Java also use interfaces. All of the methods declared in an interface are implicitly abstract:
interface C { void a_pure_virtual_function(); }
In VB.NET, pure virtual function may be declared using MustOverride keyword, which would make the class not instantiatable. If another class inherits it, it must provide an overriding method (using Overrides keyword, see above) in order to be instantiatable. Note that End Sub/ End Function/ End Property statement is not needed for pure virtual functions in VB.NET.
Public MustInherit Class Animal Public MustOverride Sub Eat() End Class
Just like C# and Java, an interface may be used instead:
Public Interface Animal Public Sub Eat() End Interface
In C# and Java, a virtual function or virtual method is included into the language syntax with a different philosophy. C# has normally functions that are non-virtual, whereas Java's methods are by default virtual, but can be made non-virtual with the final keyword. There are reasons for this selection in both cases, which may be of interest:[3]
Languages differ in their behaviour while the constructor or destructor of an object is running. For some languages, notably C++, the virtual dispatching mechanism has different semantics during construction and destruction of an object. While it is recommended that virtual function calls in constructors should be avoided for C++,[4] in some other languages, for example C# and Java, the derived implementation can be called during construction and design patterns such as the Abstract Factory Pattern actively promote this usage in languages supporting the ability.
#include <iostream> #include <string> using namespace std; struct A { virtual string name() const { return "A"; } virtual ~A() { cout << "Destructing " << name() << endl; } }; struct B : A { B() { cout << "Constructing " << name() << endl; } virtual string name() const { return "B"; } }; struct C : B { virtual string name() const { return "C"; } }; int main() { C c; // Output: "Constructing B" } // Output: "Destructing A"
public class Base { public int length() { return 0; } public Base() { System.out.println("Constructing " + length()); } static class Derived extends Base { String name; public Derived(String name) { this.name = (name != null) ? name : ""; // Class invariant this.name is not null } public int length() { return name.length(); // Assume name is not null } } public static void main(String[] args) { new Derived("Ooops"); // NullPointerException, Derived.name has not been assigned to yet } }
This is because the constructor of Base is executed before the constructor of Derived. As the constructor of Base calls length(), a null pointer exception is thrown.
Object-oriented languages typically manage memory allocation and deallocation automatically when objects are created and destroyed. However, some object-oriented languages allow a custom destructor method to be implemented, if desired. One such language is C++, and as illustrated in the following example, it is important for a C++ base class to have a virtual destructor to ensure that the destructor from the most derived class will always be called.
In the example below having no virtual destructor, deleting an instance of class B will correctly call destructors for both B and A, if the object is deleted as an instance of B. An instance of B deleted via a pointer to its base class A will produce undefined behaviour.[5] On many implementations, the destructor for B will not be called in this situation.
#include <iostream> using namespace std; class A { public: A() { } ~A() { cout << "Destroy A" << endl; } }; class B : public A { public: B() { } ~B() { cout << "Destroy B" << endl; } }; int main() { A* b1 = new B; B* b2 = new B; delete b1; // According to the C++ standard, // the behaviour of this is undefined. // Usually, only ~A() is called though b1 is an instance // of class B because ~A() is not declared virtual. delete b2; // Calls destructors ~B() and ~A() return 0; }
Possible output:
Destroy A Destroy B Destroy A
Correctly declaring the destructor for class A as virtual ~A() will ensure that the destructor for class B is called in both cases with the example above.